﻿using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; // 新增 ILogger 依赖
using Microsoft.IdentityModel.Tokens;
using PmSoft.Cache.Abstractions;
using PmSoft.Core.Domain.Auth;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;

namespace PmSoft.Web.Abstractions.Authorization;

/// <summary>
/// JWT 服务实现类，提供生成、验证和刷新 JWT Token 的功能。
/// 使用 HMAC-SHA256 算法签名，支持用户身份和角色声明，集成内存和分布式缓存（Redis）管理 Token。
/// </summary>
public class JwtService : IJwtService
{
	/// <summary>
	/// JWT 密钥，用于签名和验证 Token，必须至少 32 字节
	/// </summary>
	private readonly string _secretKey;

	/// <summary>
	/// 应用程序配置，提供 JWT 设置（如 Issuer、Audience、SecretKey）
	/// </summary>
	private readonly IConfiguration _configuration;

	/// <summary>
	/// 用户认证服务，用于验证和获取用户信息
	/// </summary>
	private readonly IUserAuthenticationService _userAuthenticationService;

	/// <summary>
	/// 可选的分布式缓存提供程序（如 Redis），用于分布式 Token 管理
	/// </summary>
	private readonly IDistributedCache? _distributedCache;

	/// <summary>
	/// 内存缓存，用于本地 Token 存储和黑名单管理
	/// </summary>
	private readonly IMemoryCache _memoryCache;

	/// <summary>
	/// 日志记录器，用于记录调试信息
	/// </summary>
	private readonly ILogger<JwtService> _logger;

	/// <summary>
	/// 是否使用分布式缓存，取决于配置中的 Jwt:UseDistributedCache 设置
	/// </summary>
	private readonly bool _useDistributedCache;

	/// <summary>
	/// Access Token 缓存键前缀
	/// </summary>
	private const string TOKEN_KEY_PREFIX = "jwt:token:";

	/// <summary>
	/// Refresh Token 缓存键前缀
	/// </summary>
	private const string REFRESH_TOKEN_KEY_PREFIX = "jwt:refresh:";

	/// <summary>
	/// 黑名单缓存键前缀
	/// </summary>
	private const string BLACKLIST_KEY_PREFIX = "jwt:blacklist:";

	/// <summary>
	/// Access Token 的默认有效期（1 小时）
	/// </summary>
	private TimeSpan AccessTokenExpiration => TimeSpan.FromMinutes(
		_configuration.GetValue<double>("Jwt:AccessTokenExpirationMinutes", 60));

	/// <summary>
	/// Refresh Token 的默认有效期（7 天）
	/// </summary>
	private TimeSpan RefreshTokenExpiration => TimeSpan.FromMinutes(
		_configuration.GetValue<double>("Jwt:RefreshTokenExpirationMinutes", 10080));

	/// <summary>
	/// 初始化 JwtService 实例，注入必要的依赖项
	/// </summary>
	/// <param name="configuration">应用程序配置，提供 JWT 设置（如 Issuer、Audience、SecretKey）</param>
	/// <param name="userAuthenticationService">用户认证服务，用于验证和获取用户信息</param>
	/// <param name="memoryCache">内存缓存，用于本地 Token 存储和黑名单管理</param>
	/// <param name="logger">日志记录器，用于记录调试信息</param>
	/// <param name="distributedCache">可选的分布式缓存提供程序，用于分布式 Token 管理</param>
	/// <exception cref="ArgumentNullException">当配置、用户认证服务、内存缓存或日志记录器为 null 时抛出</exception>
	public JwtService(
		IConfiguration configuration,
		IUserAuthenticationService userAuthenticationService,
		IMemoryCache memoryCache,
		ILogger<JwtService> logger,
		IDistributedCache? distributedCache = null)
	{
		_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
		_userAuthenticationService = userAuthenticationService ?? throw new ArgumentNullException(nameof(userAuthenticationService));
		_memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
		_logger = logger ?? throw new ArgumentNullException(nameof(logger));
		_distributedCache = distributedCache;
		_useDistributedCache = _configuration.GetValue<bool>("Jwt:UseDistributedCache");

		// 初始化密钥，确保长度至少 32 字节，否则使用默认值
		var secretKey = _configuration["Jwt:SecretKey"];
		if (string.IsNullOrEmpty(secretKey) || Encoding.UTF8.GetBytes(secretKey).Length < 32)
		{
			_secretKey = "ThisIsMySuperSecretKeyForJwt12345678!";
			_logger.LogWarning("警告：JWT SecretKey 未配置或长度不足，使用默认密钥。");
		}
		else
		{
			_secretKey = secretKey;
		}
	}

	/// <summary>
	/// 为指定用户生成新的 Access Token 和 Refresh Token，并存储到缓存
	/// </summary>
	/// <param name="user">已认证的用户对象，包含用户 ID、类型和租户信息</param>
	/// <returns>包含 Access Token 和 Refresh Token 的元组</returns>
	/// <exception cref="ArgumentNullException">当用户对象为 null 时抛出</exception>
	public async Task<(string AccessToken, string RefreshToken)> GenerateTokenAsync(IAuthedUser user)
	{
		if (user == null) throw new ArgumentNullException(nameof(user));

		// 生成 Access Token 和 Refresh Token
		var accessToken = GenerateAccessToken(user);
		var refreshToken = GenerateRefreshToken();

		// 存储到缓存
		await SaveTokenToCacheAsync(user.UserId, user.TenantType, accessToken, refreshToken);

		return (accessToken, refreshToken);
	}

	/// <summary>
	/// 刷新 Access Token，使用旧的 Access Token 和 Refresh Token 获取新的令牌对
	/// </summary>
	/// <param name="oldAccessToken">过期的 Access Token</param>
	/// <param name="refreshToken">当前的 Refresh Token</param>
	/// <returns>新的 Access Token 和 Refresh Token 的元组，如果刷新失败则返回 null</returns>
	/// <exception cref="SecurityTokenException">当旧 Access Token 或 Refresh Token 为空时抛出</exception>
	public async Task<(string NewAccessToken, string NewRefreshToken)?> RefreshTokenAsync(string oldAccessToken, string refreshToken)
	{
		// 检查输入参数是否有效
		if (string.IsNullOrWhiteSpace(oldAccessToken) || string.IsNullOrWhiteSpace(refreshToken))
			throw new SecurityTokenException("旧的 Access Token 或 Refresh Token 不能为空。");

		// 解析旧 Access Token，允许过期（同时跳过白名单检查）
		var user = await ParseTokenAsync(oldAccessToken, allowExpired: true);
		if (user == null || await IsTokenInvalidatedAsync(oldAccessToken))
		{
			_logger.LogDebug("旧 Access Token 无效或已被列入黑名单。");
			return null;
		}

		// 验证 Refresh Token 是否匹配缓存中的值
		var cachedRefreshToken = await GetRefreshTokenFromCacheAsync(user.UserId, user.TenantType);
		if (cachedRefreshToken == null || cachedRefreshToken != refreshToken)
		{
			_logger.LogDebug("Refresh Token 无效或不匹配。");
			return null;
		}

		// 生成新的 Access Token 和 Refresh Token，实现令牌旋转
		var newAccessToken = GenerateAccessToken(user);
		var newRefreshToken = GenerateRefreshToken();

		// 更新缓存中的令牌
		await SaveTokenToCacheAsync(user.UserId, user.TenantType, newAccessToken, newRefreshToken);

		// 将旧 Access Token 添加到黑名单
		await InvalidateTokenAsync(oldAccessToken);

		return (newAccessToken, newRefreshToken);
	}

	/// <summary>
	/// 解析并验证 JWT Token，返回对应的用户信息
	/// </summary>
	/// <param name="token">要解析的 JWT Token 字符串</param>
	/// <param name="allowExpired">是否允许过期令牌（用于刷新场景，默认 false）</param>
	/// <returns>解析成功且有效的 Token 返回对应的用户信息，否则返回 null</returns>
	public async Task<IAuthedUser?> ParseTokenAsync(string token, bool allowExpired = false)
	{
		// 检查 Token 是否为空
		if (string.IsNullOrWhiteSpace(token)) return null;

		// 检查 Token 是否在黑名单中
		if (await IsTokenInvalidatedAsync(token))
		{
			_logger.LogDebug($"Token {token[..8]}... 在黑名单中，无效。");
			return null;
		}

		var tokenHandler = new JwtSecurityTokenHandler();
		try
		{
			// 设置 Token 验证参数
			var validationParameters = new TokenValidationParameters
			{
				ValidateIssuer = true,
				ValidateAudience = true,
				ValidateLifetime = !allowExpired, // 刷新时允许过期
				ValidateIssuerSigningKey = true,
				ValidIssuer = _configuration["Jwt:Issuer"],
				ValidAudience = _configuration["Jwt:Audience"],
				IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey)),
				ClockSkew = TimeSpan.Zero
			};

			// 验证并解析 Token
			var principal = tokenHandler.ValidateToken(token, validationParameters, out var securityToken);
			if (securityToken is not JwtSecurityToken jwtToken) return null;

			// 提取声明信息
			var userIdStr = jwtToken.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value;
			var userType = jwtToken.Claims.FirstOrDefault(c => c.Type == "UserType")?.Value;
			var tenantType = jwtToken.Claims.FirstOrDefault(c => c.Type == "TenantType")?.Value;

			// 验证声明是否有效
			if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId) || string.IsNullOrEmpty(userType))
			{
				_logger.LogDebug("Token 声明无效：用户 ID 或类型缺失。");
				return null;
			}

			// 检查 Token 是否在白名单中
			if (!allowExpired && !await IsTokenInWhitelistAsync(token, userId, tenantType))
			{
				_logger.LogDebug($"Token {token[..8]}... 不在白名单中，无效。");
				return null;
			}

			// 获取用户信息
			var user = await _userAuthenticationService.GetAuthUserAsync(userId, tenantType);
			return user;
		}
		catch (SecurityTokenException ex)
		{
			_logger.LogDebug($"Token 验证失败：{ex.Message}");
			return null;
		}
		catch (Exception ex)
		{
			_logger.LogDebug($"解析 Token 时发生异常：{ex.Message}");
			return null;
		}
	}

	/// <summary>
	/// 检查指定的 Token 是否在白名单中有效
	/// </summary>
	/// <param name="token">要检查的 JWT Token</param>
	/// <param name="userId">用户 ID</param>
	/// <param name="tenantType">租户类型，可为空</param>
	/// <returns>如果 Token 在白名单中且匹配，则返回 true；否则返回 false</returns>
	public async Task<bool> IsTokenInWhitelistAsync(string token, int userId, string? tenantType)
	{
		if (string.IsNullOrWhiteSpace(token)) return false;

		var cachedToken = await GetUserTokenFromCacheAsync(userId, tenantType);
		return cachedToken != null && cachedToken == token;
	}

	/// <summary>
	/// 使指定用户的所有 Token 失效，并移除缓存中的记录
	/// </summary>
	/// <param name="authedUser">已认证的用户对象</param>
	/// <returns>表示异步操作的任务</returns>
	public async Task InvalidateUserTokensAsync(IAuthedUser authedUser)
	{
		var token = await GetUserTokenFromCacheAsync(authedUser.UserId, authedUser.TenantType);
		if (token != null)
		{
			// 使当前 Token 失效并加入黑名单
			await InvalidateTokenAsync(token);

			// 移除缓存中的 Access Token 和 Refresh Token
			var tokenKey = $"{TOKEN_KEY_PREFIX}{authedUser.UserId}:{authedUser.TenantType ?? string.Empty}";
			var refreshKey = $"{REFRESH_TOKEN_KEY_PREFIX}{authedUser.UserId}:{authedUser.TenantType ?? string.Empty}";
			if (_useDistributedCache && _distributedCache != null)
			{
				await _distributedCache.RemoveAsync(tokenKey);
				await _distributedCache.RemoveAsync(refreshKey);
			}
			else
			{
				_memoryCache.Remove(tokenKey);
				_memoryCache.Remove(refreshKey);
			}
		}
	}

	/// <summary>
	/// 将指定的 Token 添加到黑名单，使其失效
	/// </summary>
	/// <param name="token">要失效的 JWT Token</param>
	/// <returns>表示异步操作的任务</returns>
	public async Task InvalidateTokenAsync(string token)
	{
		var key = GetTokenCacheKey(token);
		if (_useDistributedCache && _distributedCache != null)
		{
			await _distributedCache.SetAsync(key, DateTime.UtcNow.ToString("O"), AccessTokenExpiration);
		}
		else
		{
			_memoryCache.Set(key, DateTime.UtcNow, AccessTokenExpiration);
		}
	}

	// 私有方法

	/// <summary>
	/// 生成 Access Token
	/// </summary>
	/// <param name="user">已认证的用户对象</param>
	/// <returns>生成的 Access Token 字符串</returns>
	private string GenerateAccessToken(IAuthedUser user)
	{
		var claims = new List<Claim>
		{
			new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()),
			new Claim("UserType", user.UserType.ToString()),
			new Claim("TenantType", user.TenantType ?? string.Empty)
		};

		var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
		var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

		var jwtToken = new JwtSecurityToken(
			issuer: _configuration["Jwt:Issuer"],
			audience: _configuration["Jwt:Audience"],
			claims: claims,
			expires: DateTime.UtcNow.Add(AccessTokenExpiration),
			signingCredentials: creds);

		return new JwtSecurityTokenHandler().WriteToken(jwtToken);
	}

	/// <summary>
	/// 生成 Refresh Token
	/// </summary>
	/// <returns>生成的 Refresh Token 字符串</returns>
	private string GenerateRefreshToken()
	{
		var randomNumber = new byte[32];
		using var rng = RandomNumberGenerator.Create();
		rng.GetBytes(randomNumber);
		return Convert.ToBase64String(randomNumber);
	}

	/// <summary>
	/// 将 Access Token 和 Refresh Token 保存到缓存
	/// </summary>
	/// <param name="userId">用户 ID</param>
	/// <param name="tenantType">租户类型，可为空</param>
	/// <param name="accessToken">Access Token</param>
	/// <param name="refreshToken">Refresh Token</param>
	/// <returns>表示异步操作的任务</returns>
	private async Task SaveTokenToCacheAsync(int userId, string? tenantType, string accessToken, string refreshToken)
	{
		var tokenKey = $"{TOKEN_KEY_PREFIX}{userId}:{tenantType ?? string.Empty}";
		var refreshKey = $"{REFRESH_TOKEN_KEY_PREFIX}{userId}:{tenantType ?? string.Empty}";

		if (_useDistributedCache && _distributedCache != null)
		{
			await _distributedCache.SetAsync(tokenKey, accessToken, AccessTokenExpiration);
			await _distributedCache.SetAsync(refreshKey, refreshToken, RefreshTokenExpiration);
		}
		else
		{
			_memoryCache.Set(tokenKey, accessToken, AccessTokenExpiration);
			_memoryCache.Set(refreshKey, refreshToken, RefreshTokenExpiration);
		}
	}

	/// <summary>
	/// 从缓存中获取用户的 Access Token
	/// </summary>
	/// <param name="userId">用户 ID</param>
	/// <param name="tenantType">租户类型，可为空</param>
	/// <returns>缓存中的 Access Token，若未找到则返回 null</returns>
	private async Task<string?> GetUserTokenFromCacheAsync(int userId, string? tenantType)
	{
		var key = $"{TOKEN_KEY_PREFIX}{userId}:{tenantType ?? string.Empty}";
		if (_useDistributedCache && _distributedCache != null)
		{
			return await _distributedCache.GetAsync<string>(key);
		}
		else
		{
			return _memoryCache.TryGetValue(key, out string? token) ? token : null;
		}
	}

	/// <summary>
	/// 从缓存中获取用户的 Refresh Token
	/// </summary>
	/// <param name="userId">用户 ID</param>
	/// <param name="tenantType">租户类型，可为空</param>
	/// <returns>缓存中的 Refresh Token，若未找到则返回 null</returns>
	private async Task<string?> GetRefreshTokenFromCacheAsync(int userId, string? tenantType)
	{
		var key = $"{REFRESH_TOKEN_KEY_PREFIX}{userId}:{tenantType ?? string.Empty}";
		if (_useDistributedCache && _distributedCache != null)
		{
			return await _distributedCache.GetAsync<string>(key);
		}
		else
		{
			return _memoryCache.TryGetValue(key, out string? token) ? token : null;
		}
	}

	/// <summary>
	/// 获取 Token 的黑名单缓存键，使用 SHA256 哈希生成唯一标识
	/// </summary>
	/// <param name="token">JWT Token 字符串</param>
	/// <returns>黑名单缓存键</returns>
	private string GetTokenCacheKey(string token)
	{
		using var sha256 = SHA256.Create();
		var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(token));
		return $"{BLACKLIST_KEY_PREFIX}{Convert.ToHexString(hashBytes)}";
	}

	/// <summary>
	/// 检查指定的 Token 是否在黑名单中
	/// </summary>
	/// <param name="token">要检查的 JWT Token</param>
	/// <returns>如果 Token 在黑名单中，则返回 true；否则返回 false</returns>
	private async Task<bool> IsTokenInvalidatedAsync(string token)
	{
		var key = GetTokenCacheKey(token);
		if (_useDistributedCache && _distributedCache != null)
		{
			return await _distributedCache.ExistsAsync(key);
		}
		else
		{
			return _memoryCache.TryGetValue(key, out _);
		}
	}
}

